package cn.edu.hit.kg.data;

//import cn.edu.hit.kg.Neo4jDataProvider;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by Wang on 2018/11/13.
 */
public class GraphData implements Serializable {
    private static GraphData ourInstance = null;
    protected static ObjectMapper mapper = new ObjectMapper();

    private Map<Long, Node> nodes = new ConcurrentHashMap<>();
    private Map<Long, Set<Long>> edges = new ConcurrentHashMap<>();
    //TODO 将来应加入边的信息
    private Map<String, Set<Long>> labelGroups = new ConcurrentHashMap<>();

    public void clear() {
        this.nodes.clear();
        this.edges.clear();
        this.labelGroups.clear();
    }

    public static GraphData getInstance() {
        if (ourInstance == null) {
            synchronized (GraphData.class) {
                if (ourInstance == null) {
                    reloadData();
                }
            }
        }
        return ourInstance;
    }

    public static void reloadData() {
        GraphDataProvider provider = GraphDataProvider.getInstance();
//        GraphDataProvider provider = new Neo4jDataProvider();
        if (provider == null) {
            throw new RuntimeException("知识库无法加载！！！");

        } else {
            if (ourInstance == null) {
                ourInstance = new GraphData();
            }

            ourInstance.nodes = provider.getNodes();
            ourInstance.edges = provider.getEdges();
            ourInstance.labelGroups = provider.getLabelGroups();
        }
    }

    public GraphData() {

    }

    public void saveTo(OutputStream outputStream) throws IOException {
        mapper.writeValue(outputStream, this);
    }

    public void addNode(Node node) {
        this.nodes.put(node.id(), node);
        for (String label : node.labels()) {
            Set<Long> set = this.labelGroups.getOrDefault(label, new HashSet<>());
            set.add(node.id());
            this.labelGroups.put(label, set);
        }
    }

    public Node getById(long id) {
        return this.nodes.get(id);
    }

    public Stream<Long> getAllSubNodeIds(long rootId) {
        Set<Long> subE = this.edges.get(rootId);
        if (subE == null || subE.size() == 0) {
            return Stream.empty();
        }
        return Stream.concat(subE.stream(), subE.stream().flatMap(this::getAllSubNodeIds));
    }

    public Stream<Long> getAllPreNodeIds(long childId) {
        Set<Long> subE = this.edges.entrySet().stream()
                .filter(entry -> entry.getValue().contains(childId))
                .map(Map.Entry::getKey)
                .collect(Collectors.toSet());
        if (subE == null || subE.size() == 0) {
            return Stream.empty();
        }
        return Stream.concat(subE.stream(), subE.stream().flatMap(this::getAllPreNodeIds));
    }

    public Stream<Node> getAllSubNodes(long rootId) {
        return getAllSubNodeIds(rootId).map(this::getById);
    }

    public void addEdge(long fromId, long toId) {
        if (!this.edges.containsKey(fromId)) {
            this.edges.put(fromId, new HashSet<>());
        }
        this.edges.get(fromId).add(toId);
    }

    public Map<Long, ? extends Node> getNodes() {
        return nodes;
    }

    public void setNodes(Map<Long, Node> nodes) {
        this.nodes = nodes;
    }

    public Map<Long, Set<Long>> getEdges() {
        return edges;
    }

    public void setEdges(Map<Long, Set<Long>> edges) {
        this.edges = edges;
    }

    public Map<String, Set<Long>> getLabelGroups() {
        return labelGroups;
    }

    public void setLabelGroups(Map<String, Set<Long>> labelGroups) {
        this.labelGroups = labelGroups;
    }

    public Set<Long> getIdSetByLabel(String label) {
        return this.labelGroups.getOrDefault(label, Collections.emptySet());
    }

    public Stream<Node> getByLabel(String label) {
        return labelGroups.getOrDefault(label, Collections.emptySet()).stream().map(id -> nodes.get(id));
    }

    @Override
    public String toString() {
        return "GraphData{" +
                "nodes=" + nodes +
                ", edges=" + edges +
                ", labelGroups=" + labelGroups +
                '}';
    }
}